package br.edu.ufcg.unknown.sintatico;

import java.util.LinkedList;
import java.util.List;

import br.edu.ufcg.unknown.lexico.Token;

public class IdentificaTK {

	@SuppressWarnings("unused")
	public IdentificaTK() {

	}

	/**
	 * Verifica se o token recebido ou seu lexema é nulo
	 * 
	 * @param token
	 *            a ser verificado
	 * @return true se for verificado e false caso contrário
	 */
	private boolean ehNulo(Token token) {
		return token == null || token.getLexema() == null;
	}

	/**
	 * Verifica se o token satisfaz as restrições impostas em
	 * {@link EhVerdade#eh_verdade(Token)}
	 * 
	 * @param token
	 * @param ev
	 * @return True se as restrições forem satisfeitas
	 */
	private boolean eh_(Token token, EhVerdade ev) {
		if (ehNulo(token)) {
			return false;
		}

		if (!ev.eh_verdade(token)) {
			return false;
		}

		esvaziarLexemasEsperados();
		atualizaProxToken();

		return true;
	}

	/**
	 * Verifica se o token possui o lexema esperado.
	 * 
	 * @param token
	 * @param lexemaEsperado
	 * @return True se o token possuir o lexema esperado.
	 */
	private boolean eh_(Token token, String lexemaEsperado) {
		if (ehNulo(token)) {
			return false;
		}

		if (!token.getLexema().equals(lexemaEsperado)) {
			addLexemaEsperado(lexemaEsperado);
			return false;
		}

		esvaziarLexemasEsperados();
		atualizaProxToken();

		return true;
	}

	/**
	 * TODO Refatorar estes métodos
	 */
	protected void atualizaProxToken() {
	}

	/**
	 * TODO Refatorar estes métodos
	 */
	protected void esvaziarLexemasEsperados() {
	}

	/**
	 * TODO Refatorar estes métodos
	 */
	protected void addLexemaEsperado(String lexema) {
	}

	/**
	 * Verifica se o lexema do token recebido é igual a "PROGRAM"
	 * 
	 * @param token
	 *            a ser verificado
	 * @return true se for verificado e false caso contrário
	 */
	public boolean eh_PROGRAM(Token token) {
		return eh_(token, "PROGRAM");
	}

	/**
	 * Verifica se o lexema do token recebido é igual a "INTEGER"
	 * 
	 * @param token
	 *            a ser verificado
	 * @return true se for verificado e false caso contrário
	 */
	public boolean eh_INTEGER(Token token) {
		return eh_(token, "INTEGER");
	}

	/**
	 * Verifica se o lexema do token recebido é igual a "BOOLEAN"
	 * 
	 * @param token
	 *            a ser verificado
	 * @return true se for verificado e false caso contrário
	 */
	public boolean eh_BOOL(Token token) {
		return eh_(token, "BOOLEAN");
	}

	/**
	 * Verifica se o lexema do token recebido é igual a "STRING"
	 * 
	 * @param token
	 *            a ser verificado
	 * @return true se for verificado e false caso contrário
	 */
	public boolean eh_STRING(Token token) {
		return eh_(token, "STRING");
	}

	/**
	 * Verifica se o lexema do token recebido é igual a "TRUE"
	 * 
	 * @param token
	 *            a ser verificado
	 * @return true se for verificado e false caso contrário
	 */
	public boolean eh_TRUE(Token token) {
		return eh_(token, "TRUE");
	}

	/**
	 * Verifica se o lexema do token recebido é igual a "FALSE"
	 * 
	 * @param token
	 *            a ser verificado
	 * @return true se for verificado e false caso contrário
	 */
	public boolean eh_FALSE(Token token) {
		return eh_(token, "FALSE");
	}

	/**
	 * Verifica se o lexema do token recebido é igual a "ALFA"
	 * 
	 * @param token
	 *            a ser verificado
	 * @return true se for verificado e false caso contrário
	 */
	public boolean eh_ALFA(Token token) {
		return eh_(token, new EhVerdade() {

			public boolean eh_verdade(Token token) {
				if (token.ehCadeia()) {
					return true;
				}

				addLexemaEsperado(Token.TIPO_T_CADEIA);

				return false;
			}
		});
	}

	/**
	 * Verifica se o lexema do token recebido é igual a "NUM"
	 * 
	 * @param token
	 *            a ser verificado
	 * @return true se for verificado e false caso contrário
	 */
	public boolean eh_NUM(Token token) {
		return eh_(token, new EhVerdade() {

			public boolean eh_verdade(Token token) {
				if (token.ehNumero()) {
					return true;
				}

				addLexemaEsperado(Token.TIPO_T_NUMERO);

				return false;
			}
		});
	}

	/**
	 * Verifica se o lexema do token recebido é igual a "ARRAY"
	 * 
	 * @param token
	 *            a ser verificado
	 * @return true se for verificado e false caso contrário
	 */
	public boolean eh_ARRAY(Token token) {
		return eh_(token, "ARRAY");
	}

	/**
	 * Verifica se o lexema do token recebido é igual a "OF"
	 * 
	 * @param token
	 *            a ser verificado
	 * @return true se for verificado e false caso contrário
	 */
	public boolean eh_OF(Token token) {
		return eh_(token, "OF");
	}

	/**
	 * Verifica se o lexema do token recebido é igual a "["
	 * 
	 * @param token
	 *            a ser verificado
	 * @return true se for verificado e false caso contrário
	 */
	public boolean eh_ABRE_COLCHETES(Token token) {
		return eh_(token, "[");
	}

	/**
	 * Verifica se o lexema do token recebido é igual a "]"
	 * 
	 * @param token
	 *            a ser verificado
	 * @return true se for verificado e false caso contrário
	 */
	public boolean eh_FECHA_COLCHETES(Token token) {
		return eh_(token, "]");
	}

	/**
	 * Verifica se o lexema do token recebido é igual a ";"
	 * 
	 * @param token
	 *            a ser verificado
	 * @return true se for verificado e false caso contrário
	 */
	public boolean eh_PONTO_VIRGULA(Token token) {
		return eh_(token, ";");
	}

	/**
	 * Verifica se o lexema do token recebido é igual a "="
	 * 
	 * @param token
	 *            a ser verificado
	 * @return true se for verificado e false caso contrário
	 */
	public boolean eh_IGUAL(Token token) {
		return eh_(token, "=");
	}

	/**
	 * Verifica se o lexema do token recebido é igual a ":="
	 * 
	 * @param token
	 *            a ser verificado
	 * @return true se for verificado e false caso contrário
	 */
	public boolean eh_ATRIBUICAO(Token token) {
		return eh_(token, ":=");
	}

	/**
	 * Verifica se o lexema do token recebido é igual a "CONST"
	 * 
	 * @param token
	 *            a ser verificado
	 * @return true se for verificado e false caso contrário
	 */
	public boolean eh_CONST(Token token) {
		return eh_(token, "CONST");
	}

	/**
	 * Verifica se o lexema do token recebido é igual a ":"
	 * 
	 * @param token
	 *            a ser verificado
	 * @return true se for verificado e false caso contrário
	 */
	public boolean eh_DOIS_PONTOS(Token token) {
		return eh_(token, ":");
	}

	/**
	 * Verifica se o lexema do token recebido é igual a "VAR"
	 * 
	 * @param token
	 *            a ser verificado
	 * @return true se for verificado e false caso contrário
	 */
	public boolean eh_VAR(Token token) {
		return eh_(token, "VAR");
	}

	/**
	 * Verifica se o lexema do token recebido é igual a "SUBPROGRAM"
	 * 
	 * @param token
	 *            a ser verificado
	 * @return true se for verificado e false caso contrário
	 */
	public boolean eh_SUBPROGRAM(Token token) {
		return eh_(token, "SUBPROGRAM");
	}

	/**
	 * Verifica se o lexema do token recebido é igual a "("
	 * 
	 * @param token
	 *            a ser verificado
	 * @return true se for verificado e false caso contrário
	 */
	public boolean eh_ABRE_PARENTESES(Token token) {
		return eh_(token, "(");
	}

	/**
	 * Verifica se o lexema do token recebido é igual a ")"
	 * 
	 * @param token
	 *            a ser verificado
	 * @return true se for verificado e false caso contrário
	 */
	public boolean eh_FECHA_PARENTESES(Token token) {
		return eh_(token, ")");
	}

	/**
	 * Verifica se o lexema do token recebido é igual a "SUBPROGRAMS"
	 * 
	 * @param token
	 *            a ser verificado
	 * @return true se for verificado e false caso contrário
	 */
	public boolean eh_SUBPROGRAMS(Token token) {
		return eh_(token, "SUBPROGRAMS");
	}

	/**
	 * Verifica se o lexema do token recebido é igual a "BEGIN"
	 * 
	 * @param token
	 *            a ser verificado
	 * @return true se for verificado e false caso contrário
	 */
	public boolean eh_BEGIN(Token token) {
		return eh_(token, "BEGIN");
	}

	/**
	 * Verifica se o lexema do token recebido é igual a "END"
	 * 
	 * @param token
	 *            a ser verificado
	 * @return true se for verificado e false caso contrário
	 */
	public boolean eh_END(Token token) {
		return eh_(token, "END");
	}

	/**
	 * Verifica se o lexema do token recebido é igual a "WRITE"
	 * 
	 * @param token
	 *            a ser verificado
	 * @return true se for verificado e false caso contrário
	 */
	public boolean eh_WRITE(Token token) {
		return eh_(token, "WRITE");
	}

	/**
	 * Verifica se o lexema do token recebido é igual a "READLN"
	 * 
	 * @param token
	 *            a ser verificado
	 * @return true se for verificado e false caso contrário
	 */
	public boolean eh_READLN(Token token) {
		return eh_(token, "READLN");
	}

	/**
	 * Verifica se o lexema do token recebido é igual a "IF"
	 * 
	 * @param token
	 *            a ser verificado
	 * @return true se for verificado e false caso contrário
	 */
	public boolean eh_IF(Token token) {
		return eh_(token, "IF");
	}

	/**
	 * Verifica se o lexema do token recebido é igual a "THEN"
	 * 
	 * @param token
	 *            a ser verificado
	 * @return true se for verificado e false caso contrário
	 */
	public boolean eh_THEN(Token token) {
		return eh_(token, "THEN");
	}

	/**
	 * Verifica se o lexema do token recebido é igual a "ELSE"
	 * 
	 * @param token
	 *            a ser verificado
	 * @return true se for verificado e false caso contrário
	 */
	public boolean eh_ELSE(Token token) {
		return eh_(token, "ELSE");
	}

	/**
	 * Verifica se o lexema do token recebido é igual a "WHILE"
	 * 
	 * @param token
	 *            a ser verificado
	 * @return true se for verificado e false caso contrário
	 */
	public boolean eh_WHILE(Token token) {
		return eh_(token, "WHILE");
	}

	/**
	 * Verifica se o lexema do token recebido é igual a "DO"
	 * 
	 * @param token
	 *            a ser verificado
	 * @return true se for verificado e false caso contrário
	 */
	public boolean eh_DO(Token token) {
		return eh_(token, "DO");
	}

	/**
	 * Verifica se o lexema do token recebido é igual a "<>"
	 * 
	 * @param token
	 *            a ser verificado
	 * @return true se for verificado e false caso contrário
	 */
	public boolean eh_DIFERENTE(Token token) {
		return eh_(token, "<>");
	}

	/**
	 * Verifica se o lexema do token recebido é igual a ">"
	 * 
	 * @param token
	 *            a ser verificado
	 * @return true se for verificado e false caso contrário
	 */
	public boolean eh_MAIOR(Token token) {
		return eh_(token, ">");
	}

	/**
	 * Verifica se o lexema do token recebido é igual a "<"
	 * 
	 * @param token
	 *            a ser verificado
	 * @return true se for verificado e false caso contrário
	 */
	public boolean eh_MENOR(Token token) {
		return eh_(token, "<");
	}

	/**
	 * Verifica se o lexema do token recebido é igual a "<="
	 * 
	 * @param token
	 *            a ser verificado
	 * @return true se for verificado e false caso contrário
	 */
	public boolean eh_MENOR_IGUAL(Token token) {
		return eh_(token, "<=");
	}

	/**
	 * Verifica se o lexema do token recebido é igual a ">="
	 * 
	 * @param token
	 *            a ser verificado
	 * @return true se for verificado e false caso contrário
	 */
	public boolean eh_MAIOR_IGUAL(Token token) {
		return eh_(token, ">=");
	}

	/**
	 * Verifica se o lexema do token recebido é igual a "+"
	 * 
	 * @param token
	 *            a ser verificado
	 * @return true se for verificado e false caso contrário
	 */
	public boolean eh_MAIS(Token token) {
		return eh_(token, "+");
	}

	/**
	 * Verifica se o lexema do token recebido é igual a "-"
	 * 
	 * @param token
	 *            a ser verificado
	 * @return true se for verificado e false caso contrário
	 */
	public boolean eh_MENOS(Token token) {
		return eh_(token, "-");
	}

	/**
	 * Verifica se o lexema do token recebido é igual a "OR"
	 * 
	 * @param token
	 *            a ser verificado
	 * @return true se for verificado e false caso contrário
	 */
	public boolean eh_OR(Token token) {
		return eh_(token, "OR");
	}

	/**
	 * Verifica se o lexema do token recebido é igual a "*"
	 * 
	 * @param token
	 *            a ser verificado
	 * @return true se for verificado e false caso contrário
	 */
	public boolean eh_MULTIPLICACAO(Token token) {
		return eh_(token, "*");
	}

	/**
	 * Verifica se o lexema do token recebido é igual a "/"
	 * 
	 * @param token
	 *            a ser verificado
	 * @return true se for verificado e false caso contrário
	 */
	public boolean eh_DIVISAO(Token token) {
		return eh_(token, "/");
	}

	/**
	 * Verifica se o lexema do token recebido é igual a "DIV"
	 * 
	 * @param token
	 *            a ser verificado
	 * @return true se for verificado e false caso contrário
	 */
	public boolean eh_DIV(Token token) {
		return eh_(token, "DIV");
	}

	/**
	 * Verifica se o lexema do token recebido é igual a "MOD"
	 * 
	 * @param token
	 *            a ser verificado
	 * @return true se for verificado e false caso contrário
	 */
	public boolean eh_MOD(Token token) {
		return eh_(token, "MOD");
	}

	/**
	 * Verifica se o lexema do token recebido é igual a "AND"
	 * 
	 * @param token
	 *            a ser verificado
	 * @return true se for verificado e false caso contrário
	 */
	public boolean eh_AND(Token token) {
		return eh_(token, "AND");
	}

	/**
	 * Verifica se o lexema do token recebido é igual a "NOT"
	 * 
	 * @param token
	 *            a ser verificado
	 * @return true se for verificado e false caso contrário
	 */
	public boolean eh_NOT(Token token) {
		return eh_(token, "NOT");
	}

	/**
	 * Utilizado apenas para ficar mas fácil no {@link AnalisadorSintatico} de
	 * saber que o símbolo não-terminal teve a produção vazia.
	 * 
	 * @return True sempre
	 */
	public boolean eh_LAMBDA() {
		esvaziarLexemasEsperados();
		return true;
	}

	/**
	 * Verifica se o lexema do token recebido é igual a "IDENTIFICADOR"
	 * 
	 * @param token
	 *            a ser verificado
	 * @return true se for verificado e false caso contrário
	 */
	public boolean eh_IDENTIFICADOR(Token token) {
		return eh_(token, new EhVerdade() {

			public boolean eh_verdade(Token token) {
				if (token.ehIdentificador() && !token.ehPalavraReservada()) {
					return true;
				}

				addLexemaEsperado(Token.TIPO_T_IDENT);

				return false;
			}
		});
	}

	/**
	 * Classe auxiliar pra fazer verificações em qualquer <code>Token</code>.
	 * 
	 * @author Fábio Jorge
	 * @author Jaindson Valentim
	 * @author Moisés Rodrigues
	 */
	private interface EhVerdade {

		/**
		 * @param token
		 * @return True se por acaso a verificação tiver OK.
		 */
		public boolean eh_verdade(Token token);

	}

	public List<String> getPrimeirosDeExpressao() {
		return getPrimeirosDeExpressaoSimples();
	}

	public List<String> getPrimeirosDeExpressaoSimples() {
		return getPrimeirosDeTermo();
	}

	public List<String> getPrimeirosDeExpressaoRelacional() {

		List<String> resultado = new LinkedList<String>();

		resultado.add("=");
		resultado.add("<>");
		resultado.add(">");
		resultado.add("<");
		resultado.add(">=");
		resultado.add("<=");
		resultado.add("");

		return resultado;
	}

	public List<String> getPrimeirosDeTermo() {
		return getPrimeirosDeExpressaoUnaria();
	}

	public List<String> getPrimeirosDeExpressaoAditiva() {

		List<String> resultado = new LinkedList<String>();

		resultado.add("+");
		resultado.add("-");
		resultado.add("OR");
		resultado.add("");

		return resultado;
	}

	public List<String> getPrimeirosDeExpressaoUnaria() {
		List<String> resultado = new LinkedList<String>();

		resultado.add("NOT");
		resultado.add("-");
		resultado.add("(");
		resultado.add("t_ident");
		resultado.add("t_numero");
		resultado.add("TRUE");
		resultado.add("FALSE");
		resultado.add("t_cadeia");

		return resultado;
	}

	public List<String> getPrimeirosDeExpressaoMultiplicativa() {
		List<String> resultado = new LinkedList<String>();

		resultado.add("*");
		resultado.add("/");
		resultado.add("DIV");
		resultado.add("MOD");
		resultado.add("AND");
		resultado.add("");

		return resultado;
	}

	public List<String> getPrimeirosDe(String... primeiro) {
		List<String> resultado = new LinkedList<String>();

		for (int i = 0; i < primeiro.length; i++) {
			resultado.add(primeiro[i]);
		}

		return resultado;
	}

	public List<String> getTokensDeSincronizacaoDeExpressaoUnaria() {
		List<String> resultado = new LinkedList<String>();

		resultado.addAll(getPrimeirosDeExpressaoMultiplicativa());
		resultado.addAll(getTokensDeSincronizacaoDeExpressaoMultiplicativa());

		return resultado;
	}

	public List<String> getTokensDeSincronizacaoDeExpressaoMultiplicativa() {
		List<String> resultado = new LinkedList<String>();

		resultado.addAll(getTokensDeSincronizacaoDeTermo());

		return resultado;
	}

	public List<String> getTokensDeSincronizacaoDeTermo() {
		List<String> resultado = new LinkedList<String>();

		resultado.addAll(getPrimeirosDeExpressaoAditiva());
		resultado.addAll(getTokensDeSincronizacaoDeExpressaoAditiva());

		return resultado;
	}

	public List<String> getTokensDeSincronizacaoDeExpressaoAditiva() {
		List<String> resultado = new LinkedList<String>();

		resultado.addAll(getTokensDeSincronizacaoDeExpressaoSimples());

		return resultado;
	}

	public List<String> getTokensDeSincronizacaoDeExpressaoSimples() {
		List<String> resultado = new LinkedList<String>();

		resultado.addAll(getTokensDeSincronizacaoDeExpressaoRelacional());

		return resultado;
	}

	public List<String> getTokensDeSincronizacaoDeExpressaoRelacional() {
		List<String> resultado = new LinkedList<String>();

		resultado.addAll(getTokensDeSincronizacaoDeExpressao());

		return resultado;
	}

	public List<String> getTokensDeSincronizacaoDeExpressao() {
		List<String> resultado = new LinkedList<String>();

		// XXX Generalizar fazendo referencia aos getTokensDeSincronização do
		// não terminal acima.
		resultado.add(";");
		resultado.add(")");

		return resultado;
	}

}
